/*
 * Copyright 2010-2020 Alfresco Software, Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.activiti.core.el.juel.misc;

import jakarta.el.ELException;
import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * Arithmetic Operations as specified in chapter 1.7.
 *
 * @author Christoph Beck
 */
public class NumberOperations {

    private static final Long LONG_ZERO = Long.valueOf(0L);

    private static final boolean isDotEe(String value) {
        int length = value.length();
        for (int i = 0; i < length; i++) {
            switch (value.charAt(i)) {
                case '.':
                case 'E':
                case 'e':
                    return true;
            }
        }
        return false;
    }

    private static final boolean isDotEe(Object value) {
        return value instanceof String && isDotEe((String) value);
    }

    private static final boolean isFloatOrDouble(Object value) {
        return value instanceof Float || value instanceof Double;
    }

    private static final boolean isFloatOrDoubleOrDotEe(Object value) {
        return isFloatOrDouble(value) || isDotEe(value);
    }

    private static final boolean isBigDecimalOrBigInteger(Object value) {
        return value instanceof BigDecimal || value instanceof BigInteger;
    }

    private static final boolean isBigDecimalOrFloatOrDoubleOrDotEe(
        Object value
    ) {
        return value instanceof BigDecimal || isFloatOrDoubleOrDotEe(value);
    }

    public static final Number add(
        TypeConverter converter,
        Object o1,
        Object o2
    ) {
        if (o1 == null && o2 == null) {
            return LONG_ZERO;
        }
        if (o1 instanceof BigDecimal || o2 instanceof BigDecimal) {
            return converter
                .convert(o1, BigDecimal.class)
                .add(converter.convert(o2, BigDecimal.class));
        }
        if (isFloatOrDoubleOrDotEe(o1) || isFloatOrDoubleOrDotEe(o2)) {
            if (o1 instanceof BigInteger || o2 instanceof BigInteger) {
                return converter
                    .convert(o1, BigDecimal.class)
                    .add(converter.convert(o2, BigDecimal.class));
            }
            return (
                converter.convert(o1, Double.class) +
                converter.convert(o2, Double.class)
            );
        }
        if (o1 instanceof BigInteger || o2 instanceof BigInteger) {
            return converter
                .convert(o1, BigInteger.class)
                .add(converter.convert(o2, BigInteger.class));
        }
        return (
            converter.convert(o1, Long.class) +
            converter.convert(o2, Long.class)
        );
    }

    public static final Number sub(
        TypeConverter converter,
        Object o1,
        Object o2
    ) {
        if (o1 == null && o2 == null) {
            return LONG_ZERO;
        }
        if (o1 instanceof BigDecimal || o2 instanceof BigDecimal) {
            return converter
                .convert(o1, BigDecimal.class)
                .subtract(converter.convert(o2, BigDecimal.class));
        }
        if (isFloatOrDoubleOrDotEe(o1) || isFloatOrDoubleOrDotEe(o2)) {
            if (o1 instanceof BigInteger || o2 instanceof BigInteger) {
                return converter
                    .convert(o1, BigDecimal.class)
                    .subtract(converter.convert(o2, BigDecimal.class));
            }
            return (
                converter.convert(o1, Double.class) -
                converter.convert(o2, Double.class)
            );
        }
        if (o1 instanceof BigInteger || o2 instanceof BigInteger) {
            return converter
                .convert(o1, BigInteger.class)
                .subtract(converter.convert(o2, BigInteger.class));
        }
        return (
            converter.convert(o1, Long.class) -
            converter.convert(o2, Long.class)
        );
    }

    public static final Number mul(
        TypeConverter converter,
        Object o1,
        Object o2
    ) {
        if (o1 == null && o2 == null) {
            return LONG_ZERO;
        }
        if (o1 instanceof BigDecimal || o2 instanceof BigDecimal) {
            return converter
                .convert(o1, BigDecimal.class)
                .multiply(converter.convert(o2, BigDecimal.class));
        }
        if (isFloatOrDoubleOrDotEe(o1) || isFloatOrDoubleOrDotEe(o2)) {
            if (o1 instanceof BigInteger || o2 instanceof BigInteger) {
                return converter
                    .convert(o1, BigDecimal.class)
                    .multiply(converter.convert(o2, BigDecimal.class));
            }
            return (
                converter.convert(o1, Double.class) *
                converter.convert(o2, Double.class)
            );
        }
        if (o1 instanceof BigInteger || o2 instanceof BigInteger) {
            return converter
                .convert(o1, BigInteger.class)
                .multiply(converter.convert(o2, BigInteger.class));
        }
        return (
            converter.convert(o1, Long.class) *
            converter.convert(o2, Long.class)
        );
    }

    public static final Number div(
        TypeConverter converter,
        Object o1,
        Object o2
    ) {
        if (o1 == null && o2 == null) {
            return LONG_ZERO;
        }
        if (isBigDecimalOrBigInteger(o1) || isBigDecimalOrBigInteger(o2)) {
            return converter
                .convert(o1, BigDecimal.class)
                .divide(
                    converter.convert(o2, BigDecimal.class),
                    BigDecimal.ROUND_HALF_UP
                );
        }
        return (
            converter.convert(o1, Double.class) /
            converter.convert(o2, Double.class)
        );
    }

    public static final Number mod(
        TypeConverter converter,
        Object o1,
        Object o2
    ) {
        if (o1 == null && o2 == null) {
            return LONG_ZERO;
        }
        if (
            isBigDecimalOrFloatOrDoubleOrDotEe(o1) ||
            isBigDecimalOrFloatOrDoubleOrDotEe(o2)
        ) {
            return (
                converter.convert(o1, Double.class) %
                converter.convert(o2, Double.class)
            );
        }
        if (o1 instanceof BigInteger || o2 instanceof BigInteger) {
            return converter
                .convert(o1, BigInteger.class)
                .remainder(converter.convert(o2, BigInteger.class));
        }
        return (
            converter.convert(o1, Long.class) %
            converter.convert(o2, Long.class)
        );
    }

    public static final Number neg(TypeConverter converter, Object value) {
        if (value == null) {
            return LONG_ZERO;
        }
        if (value instanceof BigDecimal) {
            return ((BigDecimal) value).negate();
        }
        if (value instanceof BigInteger) {
            return ((BigInteger) value).negate();
        }
        if (value instanceof Double) {
            return Double.valueOf(-((Double) value).doubleValue());
        }
        if (value instanceof Float) {
            return Float.valueOf(-((Float) value).floatValue());
        }
        if (value instanceof String) {
            if (isDotEe((String) value)) {
                return Double.valueOf(
                    -converter.convert(value, Double.class).doubleValue()
                );
            }
            return Long.valueOf(
                -converter.convert(value, Long.class).longValue()
            );
        }
        if (value instanceof Long) {
            return Long.valueOf(-((Long) value).longValue());
        }
        if (value instanceof Integer) {
            return Integer.valueOf(-((Integer) value).intValue());
        }
        if (value instanceof Short) {
            return Short.valueOf((short) -((Short) value).shortValue());
        }
        if (value instanceof Byte) {
            return Byte.valueOf((byte) -((Byte) value).byteValue());
        }
        throw new ELException(
            LocalMessages.get("error.negate", value.getClass())
        );
    }
}
